'''
Created on Jul 9, 2011

@author: Cihat Basol
'''       
        
        
from PyQt4.QtGui import *
from PyQt4.QtCore import SIGNAL, Qt, QModelIndex, QVariant, QAbstractTableModel

from dexen.system import client
from dexen.system.client import ui
from dexen.system.client import db
from dexen.system.common.meta import ScoreMeta
from dexen.system.common.base import Individual
from dexen.system.client.ui.widgets.table_view import TableView
from dexen.system.client.ui.dialogs.export_dlg import ExportDialog
from dexen.system.client.ui.dialogs.archive_dlg import ArchiveDialog
from dexen.system.client.controller import archive_syncher
from dexen.system.client.controller.archive_syncher import ArchiveSyncher
from dexen.system.client.controller import timeIt
from dexen.system.client.controller import action_mgr

svr_proxy = client.GetServerProxy()

class IndividualModel(QAbstractTableModel):
    def __init__(self, inds, scores, parent=None):
        super(IndividualModel, self).__init__(parent)
        self._header_items = []
        self._content_items = []
        self._content_str = ""
        self.inds = inds
        self.scores = scores # List of ScoreMeta
        self._set_header()
        self._set_content()

    def rowCount(self, index=QModelIndex()):
        return len(self._content_items)
    
    def columnCount(self, index=QModelIndex()):
        if len(self._content_items) == 0:
            return 0
        return len(self._content_items[0])

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role != Qt.DisplayRole or self._header_items == []:
            return QVariant()
        if orientation == Qt.Horizontal:
            return QVariant(self._header_items[section])
        if orientation == Qt.Vertical:
            return QVariant(section + 1)
    
    def data(self, index, role=Qt.DisplayRole):
        assert isinstance(index, QModelIndex)
        if not index.isValid(): return QVariant()
        
        item = self._content_items[index.row()][2]
                
        if role == Qt.BackgroundRole:
            if str(item) != "None":
                return QColor(Qt.lightGray)
            return QColor(0x4E, 0x92, 0x58, 128)
        
        if role == Qt.ForegroundRole:
            if str(item) != "None":
                return QColor(Qt.black)
            return QColor(Qt.black)
        
        if role == Qt.DisplayRole:
            return QVariant(self._content_items[index.row()][index.column()])
    
    def get_log(self):
        return self._content_str   

    def _set_header(self):
        """Id, Birth time, Death time, <list of scores>"""
        self._header_items = Individual._get_header().split(",")
        
        for score_meta in self.scores:
            assert isinstance(score_meta, ScoreMeta)
            self._header_items.append(score_meta.name)
        
        assert self.columnCount() == 0
        self.beginInsertColumns(QModelIndex(), 0, len(self._header_items) - 1)
        self.endInsertColumns()

        self._content_str = ",".join(self._header_items) + "\n"

    def _set_content(self):        
        self.beginInsertRows(QModelIndex(), 0, len(self.inds) - 1)

        for ind in self.inds:
            items_str = ""
            items = []
            
            assert isinstance(ind, Individual)
            log = ind._get_pareto_log()
            items_str += log 
            items.extend(log.split(","))
            
            for score_meta in self.scores:
                if hasattr(ind, score_meta.name):
                    score_val = getattr(ind, score_meta.name)
                    items_str += ",%f"%(score_val)
                else:
                    score_val = "None"
                    items_str += ",None"
                items.append(score_val)
                
            items_str += "\n"
                                
            self._content_items.append(items)
            #self._content_str.join(items_str)
            self._content_str += items_str
            #TODO: Watch out for \n for the content_str
            
        self.endInsertRows()


class SortFilterProxyModel(QSortFilterProxyModel):
    def __init__(self, parent=None):
        QSortFilterProxyModel.__init__(self, parent)
    
    def lessThan(self, left, right):
        assert isinstance(left, QModelIndex)
        assert isinstance(right, QModelIndex)
        
        leftData = self.sourceModel().data(left)
        rightData = self.sourceModel().data(right)
        
        assert isinstance(leftData, QVariant)
        assert isinstance(rightData, QVariant)
        
        if left.column() == 0 or left.column() == 1:
            return leftData.toInt() < rightData.toInt()
        
        model = self.sourceModel()
        assert isinstance(model, IndividualModel)
        
        # Death time can be None which means, it is not dead
        # Treat it like infinity
        if left.column() == 2:
            if rightData.toString() == "None":
                return True
            if leftData.toString() == "None":
                return False
            return leftData.toInt() < rightData.toInt()
        
        # score columns
        return leftData.toDouble() < rightData.toDouble()

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role != Qt.DisplayRole:
            return QVariant()
        if orientation == Qt.Horizontal:
            if self.sourceModel():
                return self.sourceModel().headerData(section, orientation, role)
            else:
                return None
        if orientation == Qt.Vertical:
            return QVariant(section + 1)

    def get_content(self):
        model = self.sourceModel()
        assert isinstance(model, IndividualModel)
        content = ",".join(model._header_items) + "\n"
        for i in xrange(self.rowCount()):
            line = ""
            cols = self.columnCount()
            for j in xrange(cols - 1):
                variant = self.data(self.index(i,j))
                assert isinstance(variant, QVariant)
                line += str(variant.toString()) + ","
            line += str(self.data(self.index(i, cols-1)).toString())
            content += line + "\n"
        return content


class IndsPage(QWidget):
    def __init__(self):
        super(IndsPage, self).__init__()
        self.cur_job = None
        self.models = {} # JobInfo: BaseTableModel
        self._create_widgets()

    def _create_widgets(self):
        # Create widgets
        self.tableView = TableView()
        self.exportBtn = QPushButton("Export")
        self.synchArchiveBtn = QPushButton("Synch Archives")
        self.refreshBtn = QPushButton("Refresh")
        
        # Configure widgets
        self.tableView.resizeColumnsToContents()
        self.tableView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.exportBtn.setToolTip("Exports the table into a csv file")
        self.synchArchiveBtn.setToolTip("Downloads archives into the client database")
        self._enable_buttons(False)
        
        # Layout widgets
        hLayout = QHBoxLayout()
        hLayout.addStretch()
        hLayout.addWidget(self.refreshBtn)
        hLayout.addWidget(self.synchArchiveBtn)
        hLayout.addWidget(self.exportBtn)
        
        layout = QVBoxLayout()
        layout.addLayout(hLayout)
        layout.addWidget(self.tableView)
        self.setLayout(layout)

        # Define Actions
        self.exportArchiveAct = QAction("Export Archive", self)

        # Configure the signal/slot connections
        self.connect(self.exportBtn, SIGNAL("clicked()"), self.on_exportBtn_clicked)
        self.connect(self.synchArchiveBtn, SIGNAL("clicked()"), self.on_synchArchivesBtn_clicked)
        self.connect(self.refreshBtn, SIGNAL("clicked()"), self.on_refreshBtn_clicked)
        self.connect(self.tableView, SIGNAL("customContextMenuRequested(const QPoint &)"), self._show_context_menu)
        self.connect(self.exportArchiveAct, SIGNAL("triggered()"), self.show_archive_dlg)

    def on_close_file(self):
        self.models.clear()
        archive_syncher.clear()
        self.cur_job = None
        self.tableView.setModel(SortFilterProxyModel())
    
    def on_connect(self):
        if self.cur_job:
            self.synchArchiveBtn.setEnabled(True)
            self.refreshBtn.setEnabled(True)
            self.cur_job.get_svr_info().connect()

    def on_disconnect(self):
        self.synchArchiveBtn.setEnabled(False)
        self.refreshBtn.setEnabled(False)
        if self.cur_job:
            self.cur_job.get_svr_info().disconnect()
    
    def cur_model(self):
        proxy_model = self.tableView.model()
        assert isinstance(proxy_model, SortFilterProxyModel)
        return proxy_model.sourceModel()
    
    def _show_context_menu(self, point):
        menu = QMenu(self.tableView)
        menu.addAction(self.exportArchiveAct)
        menu.exec_(self.tableView.mapToGlobal(point))
    
    def _get_ind_id_from_index(self, index):
        assert isinstance(index, QModelIndex)
        model = self.tableView.model()
        assert isinstance(model, SortFilterProxyModel)
        index = model.index(index.row(), 0)
        ind_id = index.data().toInt()[0]
        return ind_id
    
    def show_archive_dlg(self):
        #print "Indivdual id: ", ind_id, "clicked"
        ind_ids = {} # ind_id : None
        index_list = self.tableView.selectedIndexes()
        for index in index_list:
            ind_ids[self._get_ind_id_from_index(index)] = None
        ind_ids = ind_ids.keys()
        dlg = ArchiveDialog(ind_ids, self.cur_job, self)
        ret = dlg.exec_()
        
    def on_delete_job(self, job_info):
        if self.models.has_key(job_info):
            del self.models[job_info]
            archive_syncher.delete_syncher(job_info)

    def _enable_buttons(self, value):
        self.synchArchiveBtn.setEnabled(value)
        self.refreshBtn.setEnabled(value)
        self.exportBtn.setEnabled(value)

    def on_cur_job_changed(self, job_info):
        self._enable_buttons(job_info is not None)
        self.cur_job = job_info
        model = SortFilterProxyModel()
        if self.models.has_key(job_info):
            model.setSourceModel(self.models[job_info])
        self.tableView.setModel(model)

    @timeIt
    def on_inds_updated(self, job_info, inds, islocal):
        assert isinstance(job_info, db.JobInfo)
        scores = job_info.get_scores()
        all_inds = action_mgr.GetActionMgr(job_info).get_inds()
        model = IndividualModel(all_inds, scores)
        # Save the fresh model
        self.models[job_info] = model
        model = SortFilterProxyModel()
        model.setSourceModel(self.models[job_info])
        # Update the view based on the refreshed model
        self.tableView.setModel(model)        
        archive_syncher.GetArchiveSyncher(job_info).on_inds_updated(inds, islocal)

    def on_refreshBtn_clicked(self):
        # Call synch_data() in MainWindow class
        ui.GetMainWindow().synch_data()
            
    def on_synchArchivesBtn_clicked(self):
        model = self.cur_model()
        assert isinstance(model, IndividualModel)
        progress = QProgressDialog("Synching archive files...", "Stop", 
                                   0, model.rowCount(), self)
        progress.setWindowTitle("Synch Archive")
        progress.setWindowModality(Qt.WindowModal)

        arch_syncher = archive_syncher.GetArchiveSyncher(self.cur_job)
        assert isinstance(arch_syncher, ArchiveSyncher)
        
        num_inds_processed = 0
        last_id = 0
        while True:
            res = arch_syncher.mass_synch(last_id)
            if res["len"] == 0: break
            num_inds_processed += res["len"]
            progress.setValue(num_inds_processed)
            if progress.wasCanceled(): break
            last_id = res["last_id"]
        progress.setValue(model.rowCount())
        
    def on_exportBtn_clicked(self):
        model = self.tableView.model()
        assert isinstance(model, SortFilterProxyModel)
        content = model.get_content()
        dlg = ExportDialog(self)
        ret_val = dlg.exec_()
        if ret_val == QDialog.Accepted:
            file_path = dlg.get_file_path()
            f = open(file_path, "wb")
            f.write(content)
            f.close()
